﻿/*!
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

using System;
using System.Collections.Generic;
using System.Text;

namespace Pittypat
{
    /// <summary>
    /// 表示一个字节串值。
    /// </summary>
    public struct @binary : IPrimitive, IEnumerable<byte>
    {
        /// <summary>
        /// 将指定的 Base64 字符串或十六进制字符串解析为一个 @binary 值。
        /// </summary>
        /// <param name="s">要解析的 Base64 字符串或十六进制字符串。</param>
        /// <returns>一个 @binary 或 null。当 s 为 null 或空字符串，或者转换失败时返回 null。</returns>
        public static @binary? Parse(string s)
        {
            if (string.IsNullOrEmpty(s) || s.Length < 2)
            {
                return null;
            }

            s = s.Trim();

            if (s.Equals("empty", StringComparison.InvariantCultureIgnoreCase))
            {
                return Empty;
            }

            byte[] bytes = null;

            if (s.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
            {
                // 看作十六进制字符串
                bytes = BinaryHelper.FromHex(s, 2);
            }
            else
            {
                // 将字符串看作 base64 字符串。
                try
                {
                    bytes = Convert.FromBase64String(s);
                }
                catch
                {
                    // 尝试将字符串看作十六进制字符串
                    bytes = BinaryHelper.FromHex(s, 0);
                }
            }

            return bytes == null ? null : new @binary(bytes);
        }

        /// <summary>
        /// 表示空字节串。这是一个只读静态字段。
        /// </summary>
        public static readonly @binary Empty = new @binary(BinaryHelper.Empty);

        private byte[] value;

        /// <summary>
        /// 使用指定的字节数组初始化 @binary 结构的新实例。
        /// </summary>
        /// <param name="value">结构的值。null 与长度为 0 的串等价。</param>
        public @binary(byte[] value)
        {
            this.value = value.IsNullOrEmpty() ? BinaryHelper.Empty : value;
        }

        byte[] Value
        {
            get
            {
                return this.value ?? BinaryHelper.Empty;
            }
        }

        /// <summary>
        /// 将当前值转换为 byte[] 类型的值。
        /// </summary>
        /// <param name="value">要转换的值。</param>
        /// <returns>转换后的值。</returns>
        public static implicit operator byte[] (@binary value)
        {
            return value.value ?? BinaryHelper.Empty;
        }

        /// <summary>
        /// 将指定的值转换为 @binary 类型的值。
        /// </summary>
        /// <param name="value">要转换的值。</param>
        /// <returns>转换后的值。</returns>
        public static implicit operator @binary(byte[] value)
        {
            return new @binary(value);
        }

        /// <summary>
        /// 将指定的字符串值转换为 @binary 类型的值。
        /// </summary>
        /// <param name="value">要转换的字符串值，Base64 或十六进制字符串。</param>
        /// <returns>转换后的值。如果字符串不能转换为二进制流，则返回 Empty。</returns>
        public static explicit operator @binary(string value)
        {
            return Parse(value) ?? Empty;
        }

        /// <summary>
        /// 比较两个值是否相等。
        /// </summary>
        /// <param name="x">第一个值。</param>
        /// <param name="y">第二个值。</param>
        /// <returns>如果两个值相等，则返回 true，否则返回 false。</returns>
        public static bool operator ==(@binary x, @binary y)
        {
            if (x.Length != y.Length)
            {
                return false;
            }

            for (int i = 0; i < x.Length; ++i)
            {
                if (x[i] != y[i])
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// 比较两个值是否不相等。
        /// </summary>
        /// <param name="x">第一个值。</param>
        /// <param name="y">第二个值。</param>
        /// <returns>如果两个值不相等，则返回 true，否则返回 false。</returns>
        public static bool operator !=(@binary x, @binary y)
        {
            return !(x == y);
        }

        /// <summary>
        /// 判断两个值是否相等。
        /// </summary>
        /// <param name="obj">要比较的值。</param>
        /// <returns>如果两个值相等，则返回 true，否则返回 false。</returns>
        public override bool Equals(object obj)
        {
            if (obj is @binary)
            {
                return this == (@binary)value;
            }

            if (obj is byte[])
            {
                return BinaryHelper.Equals(this.value, (byte[])obj);
            }

            return false;
        }

        /// <summary>
        /// 返回此值的哈希代码。
        /// </summary>
        /// <returns>此值的哈希代码。</returns>
        public override int GetHashCode()
        {
            return BinaryHelper.ComputeHashCode(this.value);
        }

        /// <summary>
        /// 返回此值的字符串表示形式（base64）。
        /// </summary>
        /// <returns>此值的字符串表示形式。</returns>
        public override string ToString()
        {
            return this.ToString("B");
        }

        /// <summary>
        /// 获取一个值，该值指示当前字节串是否长度为 0。
        /// </summary>
        public bool IsEmpty
        {
            get
            {
                return this.value.IsNullOrEmpty();
            }
        }

        /// <summary>
        /// 获取当前串的长度。
        /// </summary>
        public int Length
        {
            get
            {
                return this.value == null ? 0 : this.value.Length;
            }
        }

        /// <summary>
        /// 获取指定位置处的字节。
        /// </summary>
        /// <param name="index">从 0 开始的索引位置。</param>
        /// <returns>指定位置处的字节。</returns>
        public byte this[int index]
        {
            get
            {
                return this.Value[index];
            }
        }

        #region IPrimitive Members

        object IPrimitive.Value
        {
            get
            {
                return this.Value;
            }
        }

        /// <summary>
        /// 将当前对象作为 JSON 字符串写入指定的文本流。
        /// </summary>
        /// <param name="writer">目标文本流。</param>
        /// <exception cref="ArgumentNullException">writer 是 null。</exception>
        public void Write(System.IO.TextWriter writer)
        {
            if (writer == null)
            {
                throw new ArgumentNullException("writer");
            }

            writer.Write("\"{0}\"", this.ToString("B"));
        }

        #endregion

        #region IConvertible Members

        TypeCode IConvertible.GetTypeCode()
        {
            return TypeCode.Object;
        }

        bool IConvertible.ToBoolean(IFormatProvider provider)
        {
            return this.Value.Length > 0;
        }

        byte IConvertible.ToByte(IFormatProvider provider)
        {
            return this.Value.Length > 0 ? this.Value[0] : (byte)0;
        }

        char IConvertible.ToChar(IFormatProvider provider)
        {
            return BitConverter.ToChar(BinaryHelper.PadRight(this.Value, 2), 0);
        }

        DateTime IConvertible.ToDateTime(IFormatProvider provider)
        {
            return new DateTime(BitConverter.ToInt64(BinaryHelper.PadRight(this.Value, 8), 0));
        }

        decimal IConvertible.ToDecimal(IFormatProvider provider)
        {
            return unchecked((decimal)BitConverter.ToDouble(BinaryHelper.PadRight(this.Value, 8), 0));
        }

        double IConvertible.ToDouble(IFormatProvider provider)
        {
            return BitConverter.ToDouble(BinaryHelper.PadRight(this.Value, 8), 0);
        }

        short IConvertible.ToInt16(IFormatProvider provider)
        {
            return BitConverter.ToInt16(BinaryHelper.PadRight(this.Value, 2), 0);
        }

        int IConvertible.ToInt32(IFormatProvider provider)
        {
            return BitConverter.ToInt32(BinaryHelper.PadRight(this.Value, 4), 0);
        }

        long IConvertible.ToInt64(IFormatProvider provider)
        {
            return BitConverter.ToInt64(BinaryHelper.PadRight(this.Value, 8), 0);
        }

        sbyte IConvertible.ToSByte(IFormatProvider provider)
        {
            return unchecked((sbyte)(this.Value.Length > 0 ? this.Value[0] : (byte)0));
        }

        float IConvertible.ToSingle(IFormatProvider provider)
        {
            return BitConverter.ToSingle(this.Value, 0);
        }

        string IConvertible.ToString(IFormatProvider provider)
        {
            return this.ToString();
        }

        object IConvertible.ToType(Type conversionType, IFormatProvider provider)
        {
            return ((IConvertible)((IConvertible)this).ToInt64(provider)).ToType(conversionType, provider);
        }

        ushort IConvertible.ToUInt16(IFormatProvider provider)
        {
            return BitConverter.ToUInt16(BinaryHelper.PadRight(this.Value, 2), 0);
        }

        uint IConvertible.ToUInt32(IFormatProvider provider)
        {
            return BitConverter.ToUInt32(BinaryHelper.PadRight(this.Value, 4), 0);
        }

        ulong IConvertible.ToUInt64(IFormatProvider provider)
        {
            return BitConverter.ToUInt64(BinaryHelper.PadRight(this.Value, 8), 0);
        }

        #endregion

        #region IComparable Members

        int IComparable.CompareTo(object other)
        {
            if (other is @binary)
            {
                return BinaryHelper.Compare(this.value, ((@binary)other).value);
            }

            return 1;
        }

        #endregion

        #region IFormattable Members

        /// <summary>
        /// 使用指定的格式格式化当前实例的值。
        /// </summary>
        /// <param name="format">指定要使用的格式的 String。B/b-base64，X/x-十六进制，D/d-使用逗号分隔的十进制。如果为 null，则使用“B”，如果为 String.Empty，则返回 String.Empty。</param>
        /// <param name="formatProvider">用于格式化该值的 IFormatProvider。null 表示从操作系统的当前区域设置中获取数字格式信息。</param>
        /// <returns>一个 String，包含具有指定格式的当前实例的值。</returns>
        public string ToString(string format, IFormatProvider formatProvider)
        {
            format = format ?? "";

            switch (format)
            {
                case "B":
                case "b":
                    return this.IsEmpty ? "" : Convert.ToBase64String(this.Value);
                case "X":
                    if (this.IsEmpty)
                    {
                        return "";
                    }

                    if (this.value.Length == 1)
                    {
                        return this.value[0].ToString("X2");
                    }

                    var nb1 = new StringBuilder(this.Length * 2);
                    for (int i = 0; i < this.value.Length; i++)
                    {
                        nb1.AppendFormat("{0:X2}", this.value[i]);
                    }

                    return nb1.ToString();
                case "x":
                    if (this.IsEmpty)
                    {
                        return "";
                    }

                    if (this.value.Length == 1)
                    {
                        return this.value[0].ToString("x2");
                    }

                    var nb2 = new StringBuilder(this.Length * 2);
                    for (int i = 0; i < this.value.Length; i++)
                    {
                        nb2.AppendFormat("{0:x2}", this.value[i]);
                    }

                    return nb2.ToString();
                case "D":
                case "d":
                    if (this.IsEmpty)
                    {
                        return "";
                    }

                    if (this.value.Length == 1)
                    {
                        return this.value[0].ToString();
                    }

                    var nb3 = new StringBuilder(this.Length * 4);
                    for (int i = 1; i < this.value.Length; i++)
                    {
                        nb3.AppendFormat(",{0}", this.value[i]);
                    }

                    return nb3.ToString();
                default:
                    return this.value == null || this.value.Length == 0 ? "" : Encoding.UTF8.GetString(this.value);
            }
        }

        /// <summary>
        /// 使用指定的格式格式化当前实例的值。
        /// </summary>
        /// <param name="format">指定要使用的格式的 String。B/b-base64，X/x-十六进制，D/d-使用逗号分隔的十进制。如果为 null，则使用“B”，如果为 String.Empty，则返回 String.Empty。</param>
        /// <returns>一个 String，包含具有指定格式的当前实例的值。</returns>
        public string ToString(string format)
        {
            return this.ToString(format, null);
        }

        #endregion

        #region IEnumerable<byte> Members

        /// <summary>
        /// 返回枚举此串中字节值的枚举器。
        /// </summary>
        /// <returns>枚举此串中字节值的枚举器。</returns>
        public IEnumerator<byte> GetEnumerator()
        {
            return ((IList<byte>)this.Value).GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.Value.GetEnumerator();
        }

        #endregion
    }
}
